import { walk, bfsWalk, throttle } from '../utils';
import { v4 as uuid } from 'uuid';
import {
    getAssociativeLineTargetIndex,
    computeCubicBezierPathPoints,
    cubicBezierPath,
    getNodePoint,
    computeNodePoints,
    getNodeLinePath,
} from './associativeLine/associativeLineUtils';
import associativeLineControlsMethods from './associativeLine/associativeLineControls';
import associativeLineTextMethods from './associativeLine/associativeLineText';

// 关联线插件
class AssociativeLine {
    constructor(opt = {}) {
        this.mindMap = opt.mindMap;
        this.associativeLineDraw = this.mindMap.associativeLineDraw;
        // 当前所有连接线
        this.lineList = [];
        // 当前激活的连接线
        this.activeLine = null;
        // 当前正在创建连接线
        this.isCreatingLine = false; // 是否正在创建连接线中
        this.creatingStartNode = null; // 起始节点
        this.creatingLine = null; // 创建过程中的连接线
        this.overlapNode = null; // 创建过程中的目标节点
        // 是否有节点正在被拖拽
        this.isNodeDragging = false;
        // 箭头图标
        this.markerPath = null;
        this.marker = this.createMarker();
        // 控制点
        this.controlLine1 = null;
        this.controlLine2 = null;
        this.controlPoint1 = null;
        this.controlPoint2 = null;
        this.controlPointDiameter = 10;
        this.isControlPointMousedown = false;
        this.mousedownControlPointKey = '';
        this.controlPointMousemoveState = {
            pos: null,
            startPoint: null,
            endPoint: null,
            targetIndex: '',
        };
        // 节流一下，不然很卡
        this.checkOverlapNode = throttle(this.checkOverlapNode, 100, this);
        // 控制点相关方法
        Object.keys(associativeLineControlsMethods).forEach((item) => {
            this[item] = associativeLineControlsMethods[item].bind(this);
        });
        // 关联线文字相关方法
        Object.keys(associativeLineTextMethods).forEach((item) => {
            this[item] = associativeLineTextMethods[item].bind(this);
        });
        this.bindEvent();
    }

    // 监听事件
    bindEvent() {
        this.renderAllLines = this.renderAllLines.bind(this);
        this.onDrawClick = this.onDrawClick.bind(this);
        this.onNodeClick = this.onNodeClick.bind(this);
        this.removeLine = this.removeLine.bind(this);
        this.addLine = this.addLine.bind(this);
        this.onMousemove = this.onMousemove.bind(this);
        this.onNodeDragging = this.onNodeDragging.bind(this);
        this.onNodeDragend = this.onNodeDragend.bind(this);
        this.onControlPointMouseup = this.onControlPointMouseup.bind(this);
        this.onBeforeDestroy = this.onBeforeDestroy.bind(this);

        // 节点树渲染完毕后渲染连接线
        this.mindMap.on('node_tree_render_end', this.renderAllLines);
        // 状态改变后重新渲染连接线
        this.mindMap.on('data_change', this.renderAllLines);
        // 监听画布和节点点击事件，用于清除当前激活的连接线
        this.mindMap.on('draw_click', this.onDrawClick);
        this.mindMap.on('node_click', this.onNodeClick);
        this.mindMap.on('contextmenu', this.onDrawClick);
        // 注册删除快捷键
        this.mindMap.keyCommand.addShortcut('Del|Backspace', this.removeLine);
        // 注册添加连接线的命令
        this.mindMap.command.add('ADD_ASSOCIATIVE_LINE', this.addLine);
        // 监听鼠标移动事件
        this.mindMap.on('mousemove', this.onMousemove);
        // 节点拖拽事件
        this.mindMap.on('node_dragging', this.onNodeDragging);
        this.mindMap.on('node_dragend', this.onNodeDragend);
        // 拖拽控制点
        this.mindMap.on('mouseup', this.onControlPointMouseup);
        // 缩放事件
        this.mindMap.on('scale', this.onScale);
        // 实例销毁事件
        this.mindMap.on('beforeDestroy', this.onBeforeDestroy);
    }

    // 解绑事件
    unBindEvent() {
        this.mindMap.off('node_tree_render_end', this.renderAllLines);
        this.mindMap.off('data_change', this.renderAllLines);
        this.mindMap.off('draw_click', this.onDrawClick);
        this.mindMap.off('node_click', this.onNodeClick);
        this.mindMap.off('contextmenu', this.onDrawClick);
        this.mindMap.keyCommand.removeShortcut(
            'Del|Backspace',
            this.removeLine,
        );
        this.mindMap.command.remove('ADD_ASSOCIATIVE_LINE', this.addLine);
        this.mindMap.off('mousemove', this.onMousemove);
        this.mindMap.off('node_dragging', this.onNodeDragging);
        this.mindMap.off('node_dragend', this.onNodeDragend);
        this.mindMap.off('mouseup', this.onControlPointMouseup);
        this.mindMap.off('scale', this.onScale);
        this.mindMap.off('beforeDestroy', this.onBeforeDestroy);
    }

    // 实例销毁时清除关联线文字编辑框
    onBeforeDestroy() {
        this.hideEditTextBox();
        this.removeTextEditEl();
    }

    // 画布点击事件
    onDrawClick() {
        // 取消创建关联线
        if (this.isCreatingLine) {
            this.cancelCreateLine();
        }
        // 取消激活关联线
        if (!this.isControlPointMousedown) {
            this.clearActiveLine();
        }
    }

    // 节点点击事件
    onNodeClick(node) {
        if (this.isCreatingLine) {
            this.completeCreateLine(node);
        } else {
            this.clearActiveLine();
        }
    }

    // 创建箭头
    createMarker() {
        return this.associativeLineDraw.marker(20, 20, (add) => {
            add.ref(12, 5);
            add.size(10, 10);
            add.attr('orient', 'auto-start-reverse');
            this.markerPath = add.path('M0,0 L2,5 L0,10 L10,5 Z');
        });
    }

    // 判断关联线坐标是否变更，有变更则使用变化后的坐标，无则默认坐标
    updateAllLinesPos(node, toNode, associativeLinePoint) {
        associativeLinePoint = associativeLinePoint || {};
        let [startPoint, endPoint] = computeNodePoints(node, toNode);
        let nodeRange = 0;
        let nodeDir = '';
        let toNodeRange = 0;
        let toNodeDir = '';
        if (associativeLinePoint.startPoint) {
            nodeRange = associativeLinePoint.startPoint.range || 0;
            nodeDir = associativeLinePoint.startPoint.dir || 'right';
            startPoint = getNodePoint(node, nodeDir, nodeRange);
        }
        if (associativeLinePoint.endPoint) {
            toNodeRange = associativeLinePoint.endPoint.range || 0;
            toNodeDir = associativeLinePoint.endPoint.dir || 'right';
            endPoint = getNodePoint(toNode, toNodeDir, toNodeRange);
        }
        return [startPoint, endPoint];
    }

    // 渲染所有连线
    renderAllLines() {
        // 先移除
        this.removeAllLines();
        this.removeControls();
        this.clearActiveLine();
        let tree = this.mindMap.renderer.root;
        if (!tree) return;
        let idToNode = new Map();
        let nodeToIds = new Map();
        walk(
            tree,
            null,
            (cur) => {
                if (!cur) return;
                let data = cur.getData();
                if (
                    data.associativeLineTargets &&
                    data.associativeLineTargets.length > 0
                ) {
                    nodeToIds.set(cur, data.associativeLineTargets);
                }
                if (data.uid) {
                    idToNode.set(data.uid, cur);
                }
            },
            () => {},
            true,
            0,
        );
        nodeToIds.forEach((ids, node) => {
            ids.forEach((uid, index) => {
                let toNode = idToNode.get(uid);
                if (!node || !toNode) return;
                const associativeLinePoint = (node.getData(
                    'associativeLinePoint',
                ) || [])[index];
                // 切换结构和布局，都会更新坐标
                const [startPoint, endPoint] = this.updateAllLinesPos(
                    node,
                    toNode,
                    associativeLinePoint,
                );
                this.drawLine(startPoint, endPoint, node, toNode);
            });
        });
    }

    // 绘制连接线
    drawLine(startPoint, endPoint, node, toNode) {
        let {
            associativeLineWidth,
            associativeLineColor,
            associativeLineActiveWidth,
            associativeLineActiveColor,
            associativeLineDasharray,
        } = this.mindMap.themeConfig;
        // 箭头
        this.markerPath
            .stroke({ color: associativeLineColor })
            .fill({ color: associativeLineColor });
        // 路径
        let { path: pathStr, controlPoints } = getNodeLinePath(
            startPoint,
            endPoint,
            node,
            toNode,
        );
        // 虚线
        let path = this.associativeLineDraw.path();
        path.stroke({
            width: associativeLineWidth,
            color: associativeLineColor,
            dasharray: associativeLineDasharray || [6, 4],
        }).fill({ color: 'none' });
        path.plot(pathStr);
        path.marker('end', this.marker);
        // 不可见的点击线
        let clickPath = this.associativeLineDraw.path();
        clickPath
            .stroke({ width: associativeLineActiveWidth, color: 'transparent' })
            .fill({ color: 'none' });
        clickPath.plot(pathStr);
        // 文字
        let text = this.createText({
            path,
            clickPath,
            node,
            toNode,
            startPoint,
            endPoint,
            controlPoints,
        });
        // 点击事件
        clickPath.click((e) => {
            e.stopPropagation();
            this.setActiveLine({
                path,
                clickPath,
                text,
                node,
                toNode,
                startPoint,
                endPoint,
                controlPoints,
            });
        });
        // 双击进入关联线文本编辑状态
        clickPath.dblclick(() => {
            if (!this.activeLine) return;
            this.showEditTextBox(text);
        });
        // 渲染关联线文字
        this.renderText(this.getText(node, toNode), path, text);
        this.lineList.push([path, clickPath, text, node, toNode]);
    }

    // 激活某根关联线
    setActiveLine({
        path,
        clickPath,
        text,
        node,
        toNode,
        startPoint,
        endPoint,
        controlPoints,
    }) {
        let { associativeLineActiveColor } = this.mindMap.themeConfig;
        // 如果当前存在激活节点，那么取消激活节点
        this.mindMap.execCommand('CLEAR_ACTIVE_NODE');
        // 否则清除当前的关联线的激活状态，如果有的话
        this.clearActiveLine();
        // 保存当前激活的关联线信息
        this.activeLine = [path, clickPath, text, node, toNode];
        // 让不可见的点击线显示
        clickPath.stroke({ color: associativeLineActiveColor });
        // 如果没有输入过关联线文字，那么显示默认文字
        if (!this.getText(node, toNode)) {
            this.renderText(
                this.mindMap.opt.defaultAssociativeLineText,
                path,
                text,
            );
        }
        // 渲染控制点和连线
        this.renderControls(
            startPoint,
            endPoint,
            controlPoints[0],
            controlPoints[1],
        );
        this.mindMap.emit(
            'associative_line_click',
            path,
            clickPath,
            node,
            toNode,
        );
        this.front();
    }

    // 移除所有连接线
    removeAllLines() {
        this.lineList.forEach((line) => {
            line[0].remove();
            line[1].remove();
            line[2].remove();
        });
        this.lineList = [];
    }

    // 从当前激活节点开始创建连接线
    createLineFromActiveNode() {
        if (this.mindMap.renderer.activeNodeList.length <= 0) return;
        let node = this.mindMap.renderer.activeNodeList[0];
        this.createLine(node);
    }

    // 创建连接线
    createLine(fromNode) {
        let {
            associativeLineWidth,
            associativeLineColor,
            associativeLineDasharray,
        } = this.mindMap.themeConfig;
        if (this.isCreatingLine || !fromNode) return;
        this.front();
        this.isCreatingLine = true;
        this.creatingStartNode = fromNode;
        this.creatingLine = this.associativeLineDraw.path();
        this.creatingLine
            .stroke({
                width: associativeLineWidth,
                color: associativeLineColor,
                dasharray: associativeLineDasharray || [6, 4],
            })
            .fill({ color: 'none' });
        // 箭头
        this.markerPath
            .stroke({ color: associativeLineColor })
            .fill({ color: associativeLineColor });
        this.creatingLine.marker('end', this.marker);
    }

    // 取消创建关联线
    cancelCreateLine() {
        this.isCreatingLine = false;
        this.creatingStartNode = null;
        this.creatingLine.remove();
        this.creatingLine = null;
        this.overlapNode = null;
        this.back();
    }

    // 鼠标移动事件
    onMousemove(e) {
        this.onControlPointMousemove(e);
        this.updateCreatingLine(e);
    }

    // 更新创建过程中的连接线
    updateCreatingLine(e) {
        if (!this.isCreatingLine) return;
        let { x, y } = this.getTransformedEventPos(e);
        let startPoint = getNodePoint(this.creatingStartNode);
        let offsetX = x > startPoint.x ? -10 : 10;
        let pathStr = cubicBezierPath(
            startPoint.x,
            startPoint.y,
            x + offsetX,
            y,
        );
        this.creatingLine.plot(pathStr);
        this.checkOverlapNode(x, y);
    }

    // 获取转换后的鼠标事件对象的坐标
    getTransformedEventPos(e) {
        let { x, y } = this.mindMap.toPos(e.clientX, e.clientY);
        let { scaleX, scaleY, translateX, translateY } =
            this.mindMap.draw.transform();
        return {
            x: (x - translateX) / scaleX,
            y: (y - translateY) / scaleY,
        };
    }

    // 计算节点偏移位置
    getNodePos(node) {
        const { scaleX, scaleY, translateX, translateY } =
            this.mindMap.draw.transform();
        const { left, top, width, height } = node;
        let translateLeft = left * scaleX + translateX;
        let translateTop = top * scaleY + translateY;
        return {
            left,
            top,
            translateLeft,
            translateTop,
            width,
            height,
        };
    }

    // 检测当前移动到的目标节点
    checkOverlapNode(x, y) {
        this.overlapNode = null;
        bfsWalk(this.mindMap.renderer.root, (node) => {
            if (node.getData('isActive')) {
                this.mindMap.execCommand('SET_NODE_ACTIVE', node, false);
            }
            if (node.uid === this.creatingStartNode.uid || this.overlapNode) {
                return;
            }
            let { left, top, width, height } = node;
            let right = left + width;
            let bottom = top + height;
            if (x >= left && x <= right && y >= top && y <= bottom) {
                this.overlapNode = node;
            }
        });
        if (this.overlapNode && !this.overlapNode.getData('isActive')) {
            this.mindMap.execCommand('SET_NODE_ACTIVE', this.overlapNode, true);
        }
    }

    // 完成创建连接线
    completeCreateLine(node) {
        if (this.creatingStartNode.uid === node.uid) return;
        const { beforeAssociativeLineConnection } = this.mindMap.opt;
        let stop = false;
        if (typeof beforeAssociativeLineConnection === 'function') {
            stop = beforeAssociativeLineConnection(node);
        }
        if (stop) return;
        this.addLine(this.creatingStartNode, node);
        if (this.overlapNode && this.overlapNode.getData('isActive')) {
            this.mindMap.execCommand(
                'SET_NODE_ACTIVE',
                this.overlapNode,
                false,
            );
        }
        this.cancelCreateLine();
    }

    // 添加连接线
    addLine(fromNode, toNode) {
        if (!fromNode || !toNode) return;
        // 目标节点如果没有id，则生成一个id
        let uid = toNode.getData('uid');
        if (!uid) {
            uid = uuid();
            this.mindMap.execCommand('SET_NODE_DATA', toNode, {
                uid,
            });
        }
        // 将目标节点id保存起来
        let list = fromNode.getData('associativeLineTargets') || [];
        // 连线节点是否存在相同的id,存在则阻止添加关联线
        const sameLine = list.some((item) => item === uid);
        if (sameLine) {
            return;
        }
        list.push(uid);
        // 保存控制点
        let [startPoint, endPoint] = computeNodePoints(fromNode, toNode);
        let controlPoints = computeCubicBezierPathPoints(
            startPoint.x,
            startPoint.y,
            endPoint.x,
            endPoint.y,
        );
        // 检查是否存在固定位置的配置
        const { associativeLineInitPointsPosition } = this.mindMap.opt;
        if (associativeLineInitPointsPosition) {
            const { from, to } = associativeLineInitPointsPosition;
            if (from) {
                startPoint.dir = from;
            }
            if (to) {
                endPoint.dir = to;
            }
        }
        let offsetList =
            fromNode.getData('associativeLineTargetControlOffsets') || [];
        // 保存的实际是控制点和端点的差值，否则当节点位置改变了，控制点还是原来的位置，连线就不对了
        offsetList[list.length - 1] = [
            {
                x: controlPoints[0].x - startPoint.x,
                y: controlPoints[0].y - startPoint.y,
            },
            {
                x: controlPoints[1].x - endPoint.x,
                y: controlPoints[1].y - endPoint.y,
            },
        ];
        let associativeLinePoint =
            fromNode.getData('associativeLinePoint') || [];
        // 记录关联的起始|结束坐标
        associativeLinePoint[list.length - 1] = { startPoint, endPoint };
        this.mindMap.execCommand('SET_NODE_DATA', fromNode, {
            associativeLineTargets: list,
            associativeLineTargetControlOffsets: offsetList,
            associativeLinePoint,
        });
    }

    // 删除连接线
    removeLine() {
        if (!this.activeLine) return;
        let [, , , node, toNode] = this.activeLine;
        this.removeControls();
        let {
            associativeLineTargets,
            associativeLinePoint,
            associativeLineTargetControlOffsets,
            associativeLineText,
        } = node.getData();
        associativeLinePoint = associativeLinePoint || [];
        let targetIndex = getAssociativeLineTargetIndex(node, toNode);
        // 更新关联线文本数据
        let newAssociativeLineText = {};
        if (associativeLineText) {
            Object.keys(associativeLineText).forEach((item) => {
                if (item !== toNode.getData('uid')) {
                    newAssociativeLineText[item] = associativeLineText[item];
                }
            });
        }
        this.mindMap.execCommand('SET_NODE_DATA', node, {
            // 目标
            associativeLineTargets: associativeLineTargets.filter(
                (_, index) => {
                    return index !== targetIndex;
                },
            ),
            // 连接线坐标
            associativeLinePoint: associativeLinePoint.filter((_, index) => {
                return index !== targetIndex;
            }),
            // 偏移量
            associativeLineTargetControlOffsets:
                associativeLineTargetControlOffsets
                    ? associativeLineTargetControlOffsets.filter((_, index) => {
                          return index !== targetIndex;
                      })
                    : [],
            // 文本
            associativeLineText: newAssociativeLineText,
        });
    }

    // 清除激活的线
    clearActiveLine() {
        if (this.activeLine) {
            let [, clickPath, text, node, toNode] = this.activeLine;
            clickPath.stroke({
                color: 'transparent',
            });
            // 隐藏关联线文本编辑框
            this.hideEditTextBox();
            // 如果当前关联线没有文字，则清空文字节点
            if (!this.getText(node, toNode)) {
                text.clear();
            }
            this.activeLine = null;
            this.removeControls();
            this.back();
        }
    }

    // 处理节点正在拖拽事件
    onNodeDragging() {
        if (this.isNodeDragging) return;
        this.isNodeDragging = true;
        this.lineList.forEach((line) => {
            line[0].hide();
            line[1].hide();
            line[2].hide();
        });
        this.hideControls();
    }

    // 处理节点拖拽完成事件
    onNodeDragend() {
        if (!this.isNodeDragging) return;
        this.lineList.forEach((line) => {
            line[0].show();
            line[1].show();
            line[2].show();
        });
        this.showControls();
        this.isNodeDragging = false;
    }

    // 关联线顶层显示
    front() {
        if (this.mindMap.opt.associativeLineIsAlwaysAboveNode) return;
        this.associativeLineDraw.front();
    }

    // 关联线回到原有层级
    back() {
        if (this.mindMap.opt.associativeLineIsAlwaysAboveNode) return;
        this.associativeLineDraw.back(); // 最底层
        this.associativeLineDraw.forward(); // 连线层上面
    }

    // 插件被移除前做的事情
    beforePluginRemove() {
        this.unBindEvent();
    }

    // 插件被卸载前做的事情
    beforePluginDestroy() {
        this.unBindEvent();
    }
}

AssociativeLine.instanceName = 'associativeLine';

export default AssociativeLine;
